package org.kgduyjj.toolbox;

import java.util.Date;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;
import org.kgduyjj.toolbox.enumtype.IdCardType;
import org.kgduyjj.toolbox.exception.AlgorithmsException;

/**
 * identification card tool
 * 
 * @author Kgduyjj
 * @since 2015-07-28
 * @version Alpha-1.42
 */
public class IdcardUtil {
    protected static Logger logger = Logger.getLogger(IdcardUtil.class);
    public static final String CN_DATE_PATTERN = "yyyyMMdd";
    // Chinese citizen ID number length 18。
    public static final int CN_ID_CARD_18 = 18;
    /** The minimum age limit of Chinese citizen ID card */
    public static final int CN_ID_CARD_YEAR_MIN = 1930;
    public static final String CN_MIN_CARD_DAY = "19300101";
    /** ID check code weighting factor */
    protected static final int[] POWER = { 7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2 };
    /** ID check code */
    protected static final String[] CKECK_DIGIT = { "1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2" };
    /** */
    protected static final String EMPTY = "";

    private IdcardUtil() {
    }

    /**
     * Gen a CN id cardNo
     * 
     * @param districtCode
     * @param birthday
     * @param seq
     * @return
     */
    public static String genCNCard(String districtCode, String birthday, String seq) {
        String iCard18 = "";
        String iCard17 = String.format("%s%s%s", districtCode, birthday, seq);
        char[] cArr = iCard17.toCharArray();
        if (cArr != null) {
            int[] iCard = _converCharToInt(cArr);
            int iSum17 = _getPowerSum(iCard);
            String sVal = _getCheckCode18(iSum17);
            if (sVal.length() > 0) {
                iCard18 = iCard17 + sVal;
            } else {
                return null;
            }
        }

        return iCard18;
    }

    /**
     * Validate CN IdCard
     * 
     * @param idCard
     * @return
     */
    public static boolean validateCNCard(String idCard) {
        // check idCard length is right
        if (!Pattern.matches(IdCardType.CN_CARD.getSimplePattern(), idCard)) {
            return false;
        }
        // get district string
        // if (!validateDistrict(getByType(idCard, CNCardType.DISTRICT))) {
        // return false;
        // }
        // validate birthday
        // if (!_validateDate(getByType(idCard, CNCardType.BIRTHDAY))) {
        // return false;
        // }
        // validate checkDigit is right
        if (!_validateCheckDigit(idCard)) {
            return false;
        }
        return true;
    }

    /**
     * Converts a character array into an array of numbers
     * 
     * @param ca
     * @return
     */
    protected static int[] _converCharToInt(char[] chars) {
        int len = chars.length;
        int[] iArr = new int[len];
        try {
            for (int i = 0; i < len; i++) {
                iArr[i] = Integer.parseInt(String.valueOf(chars[i]));
            }
        } catch (NumberFormatException e) {
            logger.error("Conver Char to Integer error", e);
        }
        return iArr;
    }

    /**
     * Multiplying each of the ID card and the weighted factor of the corresponding
     * position.
     * 
     * @param iArr
     * @return
     */
    protected static int _getPowerSum(int[] iArr) {
        int iSum = 0;
        if (iArr.length != 17) {
            throw new AlgorithmsException("ID Card corresponding postition length is wrong");
        }
        if (POWER.length == iArr.length) {
            for (int i = 0; i < iArr.length; i++) {
                for (int j = 0; j < POWER.length; j++) {
                    if (i == j) {
                        iSum = iSum + iArr[i] * POWER[j];
                    }
                }
            }
        }
        return iSum;
    }

    /**
     * Right after the calculation and value, then the value of the 11 modulo
     * remainder obtained, finally determine the check code.
     * 
     * @param iSum
     * @return
     */
    protected static String _getCheckCode18(int iSum) {
        int index = iSum % 11;
        try {
            if (CKECK_DIGIT[index] == null) {
                throw new AlgorithmsException("Get Check Code Error");
            }
            return CKECK_DIGIT[index];
        } catch (AlgorithmsException e) {
            throw e;
        } catch (Exception e) {
            logger.error("Get CN Card validate code error", e);
            throw new AlgorithmsException("Get Check Code Error", e);
        }
    }

    /**
     * 校验身份证长度是否为18位
     * 
     * @param idCard
     * @return
     */
    protected static boolean _validateCNCardLenght(String idCard) {
        return (idCard != null && idCard.length() == 18);
    }

    /**
     * Get IdCard top 17
     * 
     * @param idCard
     * @return
     */
    protected static String _getTop17(String idCard) {
        if (!_validateCNCardLenght(idCard)) {
            throw new AlgorithmsException("IdCard length is wrong");
        }
        return idCard.substring(0, 17);
    }

    /**
     * Get IDCard last code
     * 
     * @param idCard
     * @return
     */
    public static String _getLastCode(String idCard) {
        if (!_validateCNCardLenght(idCard)) {
            throw new AlgorithmsException("IdCard length is wrong");
        }
        return idCard.substring(17, 18);
    }

    /**
     * 数字校验
     * 
     * @param str
     * @return
     */
    protected static boolean _isDigital(String str) {
        return (str == null || EMPTY.equals(str) ? false : str.matches("^[0-9]*$"));
    }

    /**
     * validate check code.
     * 
     * @param idCard
     * @return
     */
    protected static boolean _validateCheckDigit(String idCard) {
        int[] iFullBnumber = _converCharToInt(_getTop17(idCard).toCharArray());
        int iSum17 = _getPowerSum(iFullBnumber);
        // 获取校验位
        String iCheckDigit = _getCheckCode18(iSum17);
        String checkDigit = _getLastCode(idCard);
        return checkDigit.equals(iCheckDigit);
    }

    /**
     * validate the birthday
     * 
     * @param birthday
     * @return
     */
    protected static boolean _validateBirthday(String birthday) throws AlgorithmsException {
        try {
            Date dBirthday = DateUtil.parse(birthday, CN_DATE_PATTERN);
            Date dMinDay = DateUtil.parse(CN_MIN_CARD_DAY, CN_DATE_PATTERN);

            if (dBirthday == null || dMinDay == null || dMinDay.after(dBirthday)) {
                return false;
            } else {
                return true;
            }
        } catch (Exception e) {
            logger.error("Validate IdCard Birthday Error", e);
            throw new AlgorithmsException("Validate IdCard Birthday Error");
        }
    }

    public static String getPenultimate(String idCard) throws AlgorithmsException {
        if (!_validateCNCardLenght(idCard)) {
            throw new AlgorithmsException("IdCard length is wrong");
        }
        return idCard.substring(16, 17);
    }

    /**
     * Validate the idCard is Male's;
     * 
     * @param idCard
     * @return
     * @throws AlgorithmsException
     */
    public static boolean validateMale(String idCard) throws AlgorithmsException {
        String genderPos = getPenultimate(idCard);
        if (!_isDigital(genderPos)) {
            throw new AlgorithmsException("Validate IdCard Gender Error,The 17th position isn't Digital");
        }
        return (Integer.valueOf(genderPos) % 2 != 0);
    }

    public static boolean isLastDigitTen(String idCard) throws AlgorithmsException {
        if (!_validateCNCardLenght(idCard)) {
            throw new AlgorithmsException("IdCard length is wrong");
        }
        String lastCode = _getLastCode(idCard);
        return ("x".equals(lastCode) || "X".equals(lastCode));
    }
}
